/*
 *  linux/kernel/asm.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * asm.s contains the low-level code for most hardware faults.
 * page_exception is handled by the mm, so that isn't here. This
 * file also handles (hopefully) fpu-exceptions due to TS-bit, as
 * the fpu must be properly saved/resored. This hasn't been tested.
 * 一些异常和中断的处理部分。其它硬件中断处理，在syscall.S中完成
 */

.globl divide_error,debug,nmi,int3,overflow,bounds,invalid_op
.globl double_fault,coprocessor_segment_overrun
.globl invalid_TSS,segment_not_present,stack_segment
.globl general_protection,coprocessor_error,irq13,reserved

divide_error:
	# 为什么不直接给到%eax？
	# 因为发生异常时，第一步是要保存好现场，直接给到eax会修改eax的值，导致原值丢失
	# 所以这里压栈，后面用xch指令，将eax保存到了栈中，同时又能将eax的值设置成被调函数
	# 这里做得有些复杂了，实际没必要搞成这样，直接压栈就好了，后面再从栈中直接取出要调用的函数，不必先给到eax
	pushl $do_divide_error
# 没有错误码的异常处理程序
no_error_code:
	# 取出要运行的C函数，暂时保存在eax中，同时将eax值入栈保存起来
	xchgl %eax,(%esp)

	# 现场保护:bx/cx/dx/si/di/bp/ds/es/fs
	pushl %ebx
	pushl %ecx
	pushl %edx
	pushl %edi
	pushl %esi
	pushl %ebp

	# 段寄存器保存，没有gs，可能是后面没有用到，保持原样
	# 所以就没有必要保存
	# todo: GS为什么没有保存。。。
	push %ds
	push %es
	push %fs

	# C处理函数中，要求两个参数(long esp, long error_code)
	# esp时刚发生中断时的esp地址，并非当前保存现场后的寄存器地址
	pushl $0		# "error code"，硬件没有生成错误码，也压入一个值，0.以便C函数统一处理
	lea 44(%esp),%edx # lea用来获取原中断时发生的地址，并不真的访问这个栈单元， 10个寄存器+1个错误码, 共44字节大小
	pushl %edx		# 压入栈中

	# 将数据段切换至内核, 执行内核中的程序访问内核数据
	movl $0x10,%edx	
	mov %dx,%ds
	mov %dx,%es
	mov %dx,%fs
	call *%eax	   # 调用C处理函数，此时eax保存了最开始push压入的函数地址
	addl $8,%esp	# 跳过压入的参数error_code, esp

	# 现场恢复
	pop %fs
	pop %es
	pop %ds
	popl %ebp
	popl %esi
	popl %edi
	popl %edx
	popl %ecx
	popl %ebx
	popl %eax

	# 前面用的xchg指令交换，所以这里不需要考虑现场的问题
	iret

# 其它函数只需要调用一些即可
# 相当于这些中断发生时，硬件调用的均是no_error_code
# 且硬件自动将相应的C处理函数加入进去
debug:
	pushl $do_int3		# _do_debug
	jmp no_error_code

nmi:
	pushl $do_nmi
	jmp no_error_code

int3:
	pushl $do_int3
	jmp no_error_code

overflow:
	pushl $do_overflow
	jmp no_error_code

bounds:
	pushl $do_bounds
	jmp no_error_code

invalid_op:
	pushl $do_invalid_op
	jmp no_error_code

coprocessor_segment_overrun:
	pushl $do_coprocessor_segment_overrun
	jmp no_error_code

reserved:
	pushl $do_reserved
	jmp no_error_code

#  协处理器，这个不用管它。简单处理下，然后跳到协处理器错误函数
irq13:
	pushl %eax
	xorb %al,%al

	# 以下是给8259发送EOI命令, 给CPU发响应的命令，以便后续能重新启动协处理器中断
	# 因为是外部设备，所以要发EOI，不像本文件其它的类型，非8259控制
	# 为什么是写0xF0
	outb %al,$0xF0
	movb $0x20,%al
	outb %al,$0x20
	# 加了点时间间隔再给第二片8259
	jmp 1f
1:	jmp 1f
1:	outb %al,$0xA0
	popl %eax

	# 进入协处理器程序代码中执行
	jmp coprocessor_error

# 带错误码的处理函数，即硬件会自动压入一部分错误码
double_fault:
	pushl $do_double_fault
error_code:
	xchgl %eax,4(%esp)		# error code <-> %eax
	xchgl %ebx,(%esp)		# &function <-> %ebx

	# 现场保护
	pushl %ecx
	pushl %edx
	pushl %edi
	pushl %esi
	pushl %ebp
	push %ds
	push %es
	push %fs

	pushl %eax			# error code，取错误码
	lea 44(%esp),%eax		# offset
	pushl %eax			# 发生中断前的栈值

	# 切换至内核段，因为要操作内核数据
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs
	call *%ebx			# 调用C函数
	addl $8,%esp		# 跳过error_code, esp

	# 现场恢复
	pop %fs
	pop %es
	pop %ds
	popl %ebp
	popl %esi
	popl %edi
	popl %edx
	popl %ecx
	popl %ebx
	popl %eax
	iret

# 其它函数也一样，相当于是直接调用了error_code
# 硬件自动将相应的C地址压入到了栈中
invalid_TSS:
	pushl $do_invalid_TSS
	jmp error_code

segment_not_present:
	pushl $do_segment_not_present
	jmp error_code

stack_segment:
	pushl $do_stack_segment
	jmp error_code

general_protection:
	pushl $do_general_protection
	jmp error_code

